Gatekeeper One
1. 题目要求
1 | // SPDX-License-Identifier: MIT |
2.分析
tips:参考博客
2.1让我们把解释分成三个不同的部分
门 1:
msg.sender
和tx.origin
要打开这扇门,我们必须了解
msg.sender
它们tx.origin
之间的区别。msg.sender
(address
): 消息的发送者(当前通话)tx.origin
(address
): 交易的发送方(完整的调用链)
当交易由 EOA 进行并直接与智能合约交互时,这些变量将具有相同的值。但是,如果它与中间人合约交互
A
,然后B
通过直接调用(而不是 adelegatecall
)与另一个合约交互,那么这些值将不同。在这种情况下:
msg.sender
将有 EOA 地址tx.origin``A
将有合同的地址
因为为了
gateOne
不恢复,我们需要让msg.sender != tx.origin
这意味着我们必须enter
从智能合约而不是直接从玩家的 EOA 调用。这不是挑战的一部分,但我建议您阅读我在进一步阅读中列出的关于一些安全问题和最佳实践
tx.orgin
以及何时不应使用它的内容。2号门:
gasleft()
从关于全局变量的 Solidity 文档中我们知道这是一个返回交易剩余气体
gasleft() returns (uint256)
的函数。重要的是要知道每个 Solidity 指令实际上是一系列低级 EVM 操作码的高级表示。执行操作码后
GAS
(在EVM 代码文档站点上阅读更多内容),返回值是执行后剩余的气体量,也是GAS
当前消耗2 gas的操作码。事情在这里变得过于复杂,因为要通过检查,
gateTwo
您必须调用level.enter{gas: exactAmountOfGas}(gateKey)
非常特定数量的气体,以便gasleft().mod(8191)
返回0
(剩余的气体必须是 8191 的倍数)。你猜不到这个数字,因为你需要翻译 EVM 操作码中的所有 Solidity 代码,计算它们各自消耗的 gas 并浪费大量时间(除非你的目标也是掌握 EVM,但对于这个主题有还有大量其他资源,例如让我们玩 EVM 谜题——边玩边学习以太坊 EVM!)。您还需要记住,gas 成本可能会有所不同,具体取决于使用哪个 Solidity 编译器版本将代码编译为字节码以及在此过程中使用了哪些编译标志。一团糟。
我们可以做什么?好吧,我们可以用简单的方法去暴力破解它!按照cmichel 的建议,我们可以利用我们正在使用本地测试环境(或分叉的环境)这一事实。
我们知道交易使用的 gas
enter
必须至少为 8191 加上执行这些操作码所花费的所有 gas。我们可以进行范围猜测并对其进行暴力破解,直到它起作用为止。这是代码示例:1
2
3
4
5
6for (uint256 i = 0; i <= 8191; i++) {
try victim.enter{gas: 800000 + i}(gateKey) {
console.log("passed with gas ->", 800000 + i);
break;
} catch {}
}你从一个基本的 gas 值开始只是为了确保交易不会因为 Out of Gas 异常而恢复,然后你试图找到哪个 gas 值可以使交易成功。
在我们的例子中(solidity 编译器 + 优化标志)正确的 gas 值是:802929
关卡 3:铸造如何在 Solidity 中工作
要解决最终关口,我们首先需要了解从一种类型到另一种类型的转换以及向下转换的工作原理。Solidity 文档对其进行了很好的解释:
当您从较小的类型转换为较大的类型时,没有问题。所有的高位都用零填充,值不变。问题是当您将较大的类型转换为较小的类型时。根据值的不同,您可能会遇到数据丢失的情况,因为那些高阶位会丢失并被截断。例如,
uint16(0x0101)
是257
十进制的,但如果你向下转换它,uint8
它将是1
十进制的!2.2 参考视频 写的攻击合约
1 | interface IGateKeeperOne { |
3.解题
3.1 获取关卡实例地址:0xAd682B7a072dc407361a23D0C9Ee9f1C16dEa187
3.2 部署攻击合约,调用enter() 函数,将关卡实例地址传入enter() 函数中,并设置gas = 256
3.3 提交案例
3.4 成功!!!!!